# -*- coding:utf-8 -*-
# @File    : checkResult.py
# ***************************
import re,json,jsonpath
import allure
import operator
import logging
from decimal import Decimal
from comm.unit import readRelevance, replaceRelevance
from ast import literal_eval

def check_json(src_data, dst_data, key=None, _type=None):

    if isinstance(src_data, dict):
        for k, v in src_data.items():
            src_res = jsonpath.jsonpath(src_data, '$..{}'.format(k))
            for s_data in src_res:
                if isinstance(s_data, dict):
                    check_json(s_data, dst_data, k, _type)
                elif isinstance(s_data, list):
                    check_json(s_data, dst_data, k, _type)
                else:
                    r =  str(jsonpath.jsonpath(dst_data, '$..{}'.format(k)))
                    if _type == 1:
                        if "match[" in str(s_data):
                            matchVar(s_data, r)
                        elif str(s_data) not in str(r):
                            raise Exception("校验内容没有在实际结果中：\n期望{}:\n{} \n实际:\n {}".format(k,s_data, r))
                    else:
                        if "match[" in str(s_data):
                            matchVar(s_data, r)
                        elif str(s_data) not in r:
                            raise Exception("校验内容不等于实际结果1：\n期望{}:\n{} \n实际:\n {}".format(k, s_data, r))
    elif isinstance(src_data, list):
        for sl_data in src_data:
            if isinstance(sl_data, dict):
                check_json(sl_data, dst_data, key, _type)
            elif isinstance(sl_data, list):
                check_json(sl_data, dst_data, key, _type)
            else:
                r = str(jsonpath.jsonpath(dst_data, '$..{}'.format(key)))
                if _type == 1:
                    if "match[" in str(sl_data):
                        matchVar(sl_data, r)
                    elif str(sl_data) not in str(r):
                        raise Exception("校验内容没有在实际结果中：\n期望{}:\n{} \n实际:\n {}".format(key,sl_data, r))
                else:
                    if "match[" in str(sl_data):
                        matchVar(sl_data, r)
                    elif str(sl_data) not in r:
                        raise Exception("校验内容不等于实际结果2：\n期望{}:\n{} \n实际:\n {}".format(key,sl_data, r))
    else:
        r = str(jsonpath.jsonpath(dst_data, '$..{}'.format(key)))
        print("src_data,r:",src_data,r)
        if _type == 1:
            if "match[" in src_data:
                matchVar(src_data,r)
            elif str(src_data) not in r:
                raise Exception("校验内容没有在实际结果中：\n期望{}:\n{} \n实际:\n {}".format(key,src_data, r))
        else:
            if "match[" in src_data:
                matchVar(src_data, r)
            elif str(src_data) != r:
                raise Exception("校验内容不等于实际结果3：\n期望{}:\n{} \n实际:\n {}".format(key,src_data, r))


def matchVar(src,dst):
    '''
    match[int]:匹配整形数字
    match[s_int]:匹配字符串类型整形数字
    match[str]:匹配字符串类型
    match[float]:匹配浮点类型
    :return:
    '''

    dst = str(dst).lstrip("[").rstrip("]")
    l = dst.split(',')
    for dst in l:
        dst = str(dst.replace("'","").replace(' ',''))
        if src == 'match[int]':
            if not isinstance(dst,int):
                raise Exception("校验内容不是整形数据,\n 实际:{}".format(dst))
        elif src == 'match[int]':
            if not isinstance(dst, str):
                raise Exception("校验内容不是字符数据,\n 实际:{}".format(dst))
        elif src == 'match[float]':
            if not isinstance(dst,float):
                raise Exception("校验内容不是浮点数据,\n 实际:{}".format(dst))
        elif src == 'match[s_int]':
            if not dst.isdigit():
                print(dst,type(dst))
                raise Exception("校验内容不是数字字符串,\n 实际: {}".format(dst))





def check_json1(src_data, dst_data, _type=None):
    if isinstance(src_data, dict):
        for src, dst in zip(sorted(src_data), sorted(dst_data)):
            src_res = jsonpath.jsonpath(src_data, '$..{}'.format(src))
            dst_res = jsonpath.jsonpath(dst_data, '$..{}'.format(dst))
            for s_data, d_data in zip(src_res, dst_res):
                if isinstance(s_data, dict):
                    check_json(s_data, d_data, _type)
                elif isinstance(s_data, list):
                    check_json(s_data, d_data, _type)
                else:
                    if _type == 1:
                        if str(s_data) not in str(d_data):
                            raise Exception("校验内容没有在实际结果中：\n期望:\n{} \n实际:\n {}".format(s_data, d_data))
                    else:
                        if str(s_data) != str(d_data):
                            raise Exception("校验内容与实际结果不符：\n期望:\n{} \n实际:\n {}".format(s_data, d_data))
    elif isinstance(src_data, list):
        print("list src_data", src_data)
        for s_data, d_data in zip(src_data, dst_data):
            print("list s_data", s_data)
            if isinstance(s_data, dict):
                check_json(s_data, d_data, _type)
            elif isinstance(s_data, list):
                check_json(s_data, d_data, _type)
            else:
                if _type == 1:
                    if str(s_data) not in str(d_data):
                        raise Exception("校验内容没有在实际结果中：\n期望:\n{} \n实际:\n {}".format(s_data, d_data))
                else:
                    if str(s_data) != str(d_data):
                        raise Exception("校验内容与实际结果不符：\n期望:\n{} \n实际:\n {}".format(s_data, d_data))
    else:
        if _type == 1:
            if str(src_data) not in str(dst_data):
                raise Exception("校验内容没有在实际结果中：\n期望:\n {} \n实际:\n {}".format(src_data, dst_data))
        else:
            if str(src_data) != str(dst_data):
                raise Exception("校验内容与实际结果不符：\n期望:\n{} \n实际:\n {}".format(src_data, dst_data))


def check_database(actual, expected, mark=''):
    """校验数据库

    :param actual: 实际结果
    :param expected: 期望结果
    :param mark: 标识
    :return:
    """
    if isinstance(actual, dict) and isinstance(expected, dict):
        result = list()
        logging.info('校验数据库{}>>>'.format(mark))
        content = '\n%(key)-20s%(actual)-40s%(expected)-40s%(result)-10s' \
                % {'key': 'KEY', 'actual': 'ACTUAL', 'expected': 'EXPECTED', 'result': 'RESULT'}
        for key in expected:
            if key in actual:
                actual_value = actual[key]
            else:
                actual_value = None
            expected_value = expected[key]
            if actual_value or expected_value:
                if isinstance(actual_value, (int, float, Decimal)):
                    if int(actual_value) == int(expected_value):
                        rst = 'PASS'
                    else:
                        rst = 'FAIL'
                else:
                    if str(actual_value) == str(expected_value):
                        rst = 'PASS'
                    else:
                        rst = 'FAIL'
            else:
                rst = 'PASS'
            result.append(rst)
            line = '%(key)-20s%(actual)-40s%(expected)-40s%(result)-10s' \
                % {'key': key, 'actual': str(actual_value) + ' ',
                            'expected': str(expected_value) + ' ', 'result': rst}
            content = content + '\n' + line
        logging.info(content)
        allure.attach(name="校验数据库详情{}".format(mark[-1]), body=str(content))
        if 'FAIL' in result:
            raise AssertionError('校验数据库{}未通过！'.format(mark))

    elif isinstance(actual, list) and isinstance(expected, list):
        result = list()
        logging.info('校验数据库{}>>>'.format(mark))
        content = '\n%(key)-25s%(actual)-35s%(expected)-35s%(result)-10s' \
                % {'key': 'INDEX', 'actual': 'ACTUAL', 'expected': 'EXPECTED', 'result': 'RESULT'}
        for index in range(len(expected)):
            if index < len(actual):
                actual_value = actual[index]
            else:
                actual_value = None
            expected_value = expected[index]
            if actual_value or expected_value:
                if isinstance(actual_value, (int, float, Decimal)):
                    if int(actual_value) == int(expected_value):
                        rst = 'PASS'
                    else:
                        rst = 'FAIL'
                else:
                    if str(actual_value) == str(expected_value):
                        rst = 'PASS'
                    else:
                        rst = 'FAIL'
            else:
                rst = 'PASS'
            result.append(rst)
            line = '%(key)-25s%(actual)-35s%(expected)-35s%(result)-10s' \
                % {'key': index, 'actual': str(actual_value) + ' ',
                            'expected': str(expected_value) + ' ', 'result': rst}
            content = content + '\n' + line
        logging.info(content)
        allure.attach(name="校验数据库详情{}".format(mark[-1]), body=str(content))
        if 'FAIL' in result:
            raise AssertionError('校验数据库{}未通过！'.format(mark))

    else:
        logging.info('校验数据库{}>>>'.format(mark))
        logging.info('ACTUAL: {}\nEXPECTED: {}'.format(actual, expected))
        if str(expected) != str(actual):
            raise AssertionError('校验数据库{}未通过！'.format(mark))


def check_result(case_data, code, data):
    """
    校验测试结果
    :param case_data: 用例数据
    :param code: 接口状态码
    :param data: 返回的接口json数据
    :return:
    """
    try:
        # 获取用例检查信息
        check_type = case_data['check_body']['check_type']
        expected_code = case_data['check_body']['expected_code']
        expected_result = case_data['check_body']['expected_result']
        if '{' in expected_result:
            expected_result = literal_eval(expected_result)
    except Exception as e:
        raise KeyError('获取用例检查信息失败：{}'.format(e))

    # 接口数据校验
    if check_type == 'no_check':
        with allure.step("不校验接口结果"):
            pass

    elif check_type == 'check_code':
        with allure.step("仅校验接口状态码"):
            allure.attach(name="实际code", body=str(code))
            allure.attach(name="期望code", body=str(expected_code))
            allure.attach(name='实际data', body=str(data))
        if int(code) != int(expected_code):
            raise Exception("接口状态码错误！\n %s != %s" % (code, expected_code))

    elif check_type == 'check_json':
        with allure.step("JSON格式校验接口"):
            allure.attach(name="实际code", body=str(code))
            allure.attach(name="期望code", body=str(expected_code))
            allure.attach(name='实际data', body=str(data))
            allure.attach(name='期望data', body=str(expected_result))
        if int(code) == expected_code:
            if not data:
                data = "{}"
            check_json(expected_result, data)
        else:
            raise Exception("接口状态码错误！\n %s != %s" % (code, expected_code))

    elif check_type == 'check_json_contain':
        with allure.step("JSON格式校验接口,检查值中的value值中被包含在实际返回值中就可以"):
            allure.attach(name="实际code", body=str(code))
            allure.attach(name="期望code", body=str(expected_code))
            allure.attach(name='实际data', body=str(data))
            allure.attach(name='期望data', body=str(expected_result))
        if int(code) == expected_code:
            if not data:
                data = "{}"
            check_json(expected_result, data ,key=None, _type=None)
        else:
            raise Exception("接口状态码错误！\n %s != %s" % (code, expected_code))

    elif check_type == 'entirely_check':
        with allure.step("完全校验接口结果"):
            allure.attach(name="实际code", body=str(code))
            allure.attach(name="期望code", body=str(expected_code))
            allure.attach(name='实际data', body=str(data))
            allure.attach(name='期望data', body=str(expected_result))
        if int(code) == expected_code:
            result = operator.eq(expected_result, data)
            if not result:
                raise Exception("完全校验失败！ %s ! = %s" % (expected_result, data))
        else:
            raise Exception("接口状态码错误！\n %s != %s" % (code, expected_code))

    elif check_type == 'regular_check':
        if int(code) == expected_code:
            try:
                result = ""
                if isinstance(expected_result, list):
                    for i in expected_result:
                        result = re.findall(i.replace("\"", "\""), str(data))
                        allure.attach('校验完成结果\n', str(result))
                else:
                    result = re.findall(expected_result.replace("\"", "\'"), str(data))
                    with allure.step("正则校验接口结果"):
                        allure.attach(name="实际code", body=str(code))
                        allure.attach(name="期望code", body=str(expected_code))
                        allure.attach(name='实际data', body=str(data))
                        allure.attach(name='期望data', body=str(expected_result).replace("\'", "\""))
                        allure.attach(name=expected_result.replace("\"", "\'") + '校验完成结果',
                                      body=str(result).replace("\'", "\""))
                if not result:
                    raise Exception("正则未校验到内容！ %s" % expected_result)
            except KeyError:
                raise Exception("正则校验执行失败！ %s\n正则表达式为空时" % expected_result)
        else:
            raise Exception("接口状态码错误！\n %s != %s" % (code, expected_code))
    elif check_type == 'check_string':
        with allure.step("检验关键字符串在返回值中"):
            allure.attach(name="实际code", body=str(code))
            allure.attach(name="期望code", body=str(expected_code))
            allure.attach(name='实际data', body=str(data))
            allure.attach(name='期望data', body=str(expected_result))
        if int(code) == expected_code:
            if str(expected_result) not in str(data):
                    raise Exception("关键字符校验失败，返回值中无该字段！ %s ! = %s" % (expected_result, data))
            else:
                allure.attach('校验完成结果\n', str(expected_result))
        else:
            raise Exception("接口状态码错误！\n %s != %s" % (code, expected_code))
    elif check_type == "check_not_in":
        with allure.step("检验关键字符串在返回值中"):
            allure.attach(name="实际code", body=str(code))
            allure.attach(name="期望code", body=str(expected_code))
            allure.attach(name='实际data', body=str(data))
            allure.attach(name='期望data', body=str(expected_result))
        if int(code) == expected_code:
            if str(expected_result) in str(data):
                    raise Exception("关键字符校验失败，返回值中无该字段！ %s ! = %s" % (expected_result, data))
            else:
                allure.attach('校验完成结果\n', str(expected_result))
        else:
            raise Exception("接口状态码错误！\n %s != %s" % (code, expected_code))
    else:
        raise Exception("无该接口校验方式%s" % check_type)



    # 判断是否存在数据库校验标识
    if 'check_db' in case_data:
        from comm.unit import queryDatabase as qdb
        check_db = case_data['check_db']
        # 获取数据库期望结果：获取期望结果-获取关联值-替换关联值
        # data['parameter'] = case_data['parameter']
        relevance = readRelevance.get_relevance(data, check_db)
        check_db = replaceRelevance.replace(check_db, relevance)

        # 循环校验数据库
        for each in check_db:
            try:
                check_type = each['check_type']
                execute_sql = each['execute_sql']
                expected_result = each['expected_result']
            except KeyError as e:
                raise KeyError('【check_db】存在错误字段！\n{}'.format(e))
            except TypeError:
                raise KeyError("【check_db】类型错误，期望<class 'list'>，而不是%s！" % type(expected_result))
            if not isinstance(expected_result, list):
                raise KeyError("【expected_result】类型错误，期望<class 'list'>，而不是%s！" % type(expected_result))

            # 检索SQL语句
            exp = r"^select (.*?) from (.*?) where (.*?)$"
            res = re.findall(exp, execute_sql.strip())[0]
            for r in res:
                if not each:
                    msg = '标准格式: ' + exp
                    raise Exception('无效SQL>>> {}\n{}'.format(execute_sql, msg))
            # 判断数据库检查类型
            if check_type == 'mysql':
                actual = qdb.query_mysql(execute_sql)
            elif check_type == 'hbase':
                actual = qdb.query_hbase(execute_sql)
            elif check_type == 'solr':
                actual = qdb.query_solr(execute_sql)
            elif check_type == 'es':
                actual = qdb.query_es(execute_sql)
            else:
                raise Exception("无该数据库校验方式%s" % check_type)

            # 增加输出并进行数据校验
            mark = check_type.replace('check_', '').upper() + '['+res[1]+']'
            with allure.step("校验数据库{}".format(mark)):
                allure.attach(name="实际结果", body=str(actual))
                allure.attach(name='期望结果', body=str(expected_result))
                # expected_num = each['expected_num']
                # allure.attach(name="实际行数", body=str(len(actual)))
                # allure.attach(name='期望行数', body=str(expected_num))
                # # 验证数据库实际结果数量是否正确
                # if len(actual) != int(expected_num):
                #     raise AssertionError('校验数据库{}行数未通过！'.format(mark))
                # 检查实际结果中第一条结果值 ***************
                for index, expected in enumerate(expected_result):
                    try:
                        check_database(actual[index], expected, mark+str(index))
                    except IndexError:
                        raise IndexError('校验数据库{}失败，期望结果超出实际条目！'.format(mark+str(index)))
